/**
 * @file   Matrix.h
 * @author Shao <student@student>
 * @date   Thu Oct  7 10:57:06 2021
 * 
 * @brief  
 * 
 * 
 */

#ifndef _XLF_MATRIX
#define _XLF_MATRIX

#include <iostream>
#include <new>
#include <vector>
#include <exception>
#include <cmath>
#include <limits>

/*--------------------------------- Class Declaration -----------------------------*/
/**
 * @brief Matrix class
 * @tparam T elements in the matrix . Such as int, float, double
 */
template <typename T>
class Matrix;

/**
 * @brief  Row vector class
 * @tparam T elements in the row vector
 */
template < typename T >
class RowVector;

/**
 * @brief Column vector class
 * @tparam T elements in the column vector
 */
template <typename T >
class ColVector;

/*--------------------------------- Overload function Declaration -----------------------------*/
template <typename T>
Matrix<T> operator*(const Matrix<T>& a,const  Matrix<T>& b);

template <typename T>
Matrix<T> operator*(const ColVector<T>& a, const RowVector<T>& b);

template <typename T>
T operator*(const RowVector<T>& a,const  ColVector<T>& b);

template <typename T>
T operator*(const RowVector<T>& a, const RowVector<T>& b);

template <typename T>
T operator*(const ColVector<T>& a,const ColVector<T>& b);
/*--------------------------------------- Base Class ------------------------------------------*/
template <typename T>
class Matrix
{
    protected:
        int n_row;
        int n_col;
        T* matrix_elements;

    public:
        /**
         * @brief Construct a new Matrix object
         */
        Matrix()
        {
            matrix_elements = nullptr;
        }
        /**
         * @brief Construct a new Matrix object
         * @param  r                row number
         * @param  c                col
         */
        Matrix(int r, int c)
        {
            n_row = r;
            n_col = c;
            AllocMem(r*c);
        }

        /**
         * @brief Destroy the Matrix object
         */
        ~Matrix()
        {
            ReleaseMem();
        }

        /**
         * @brief erorr list
         * @param  level            the kind of error
         */
        void Error(int level)
        {
            switch (level)
            {
                case 0:
                    std::cout << "Allocating memory fail!" << std::endl;
                    break;

                case 1:
                    std::cout <<  " " << std::endl;
                    break;

                case 2:
                    std::cout <<  " " << std::endl;
                    break;

                case 3:
                    std::cout <<  " Unmatched matrice ! " << std::endl;
                    break;

                case 4:
                    std::cout <<  " numbers of vector elements do not match ! " << std::endl;
                    break;

                default:
                    break;
            }

        }

        /**
         * @brief  allocate memory for matrix elements
         * @param  n                My Param doc
         */
        void AllocMem(int n)
        {   
            try{matrix_elements = new T [n];}
            catch (std::bad_alloc& no_memory)
            {   
	            std::cerr << no_memory.what() << std::endl;
	            Error(0);
            }
            matrix_elements = new T[n];
        }

        /**
         * @brief clear the memory
         */
        void ReleaseMem()
        {
            if (matrix_elements != nullptr)
	        {
                delete[] matrix_elements;
                matrix_elements = nullptr;
            }
        }

        /**
         * @brief from a array
         * @param  input            My Param doc
         * @param  m                row number
         * @param  n                column number
         */
        void Inputdata(const double* input, int m, int n)
        {
            for (int i = 0; i < m ; i++)
                for(int j = 0; j < n ; j++)
                    matrix_elements[ i * n + j ] = input[i*n + j];
        };

        /**
         * @brief print the matrix on the console in the matrix form
         */
        void log()
        {
            for(int i=0 ; i < n_row; i++ )
            {
                for(int j = 0; j < n_col; j++)
                {
                    std::cout<< *( matrix_elements + i * n_col + j )<<" ";
                }
                std::cout<<std::endl;
            }
        }

        /**
         * @brief  test whether a matrix is square
         * @return true if matrix is square
         * @return false if matrix is not square
         */
        bool is_square()
        {
            bool ans = false;
            if (n_row  == n_col)
                ans = true;
            return ans;
        }

        friend 
        Matrix<T> operator * <> (const Matrix<T>& a, const Matrix<T>& b);
        friend
        Matrix<T> operator* <> (const ColVector<T>& a,const  RowVector<T>& b);
        friend
        T operator* <> (const RowVector<T>& a,const  ColVector<T>& b);
        friend
        T operator* <> (const RowVector<T>& a,const  RowVector<T>& b);
        friend
        T operator* <> (const ColVector<T>& a, const ColVector<T>& b);
};
/*-----------------------------------------------------------------------*/
/**
 * @brief row vector class derived from matrix class
 * @tparam T 
 */
template < typename T >
class RowVector : public Matrix<T>
{
    public:
        RowVector()
        {
            this->matrix_elements = nullptr;
        }

        RowVector(int n)
        {
            this->n_col = n;
            this->n_row = 1;
            this->AllocMem(n);
        }

        ~RowVector()
        {
            this->ReleaseMem();
        }
        void Inputdata(const double* input, int m)
        {
            for (int i = 0; i < m ; i++)
                this->matrix_elements[ i ] = input[ i ];
        };
        
        friend 
        Matrix<T> operator * <> (const Matrix<T>& a,const  Matrix<T>& b);
        friend
        Matrix<T> operator* <> (const ColVector<T>& a, const RowVector<T>& b);
        friend
        T operator* <> (const RowVector<T>& a, const ColVector<T>& b);
        friend
        T operator* <> (const RowVector<T>& a, const RowVector<T>& b);
        friend
        T operator* <> (const ColVector<T>& a, const ColVector<T>& b);
};

/**
 * @brief row vector class derived from matrix class
 * @tparam T 
 */
template <typename T >
class ColVector: public Matrix<T>
{   
    public:
        ColVector()
        {
            this->matrix_elements = nullptr;
        }
        ColVector(int n)
        {
            this->n_col = 1;
            this->n_row = n;
            this->AllocMem(n);
        }
        ~ColVector()
        {
            this->ReleaseMem();
        }
        void Inputdata(const double* input, int m)
        {
            for (int i = 0; i < m ; i++)
                this->matrix_elements[ i ] = input[ i ];
        };
        friend 
        Matrix<T> operator * <> (const Matrix<T>& a, const Matrix<T>& b);
        friend
        Matrix<T> operator* <> (const ColVector<T>& a, const RowVector<T>& b);
        friend
        T operator* <> (const RowVector<T>& a, const ColVector<T>& b);
        friend
        T operator* <> (const RowVector<T>& a,const  RowVector<T>& b);
        friend
        T operator* <> (const ColVector<T>& a, const ColVector<T>& b);

};

/*--------------------------- Function definition -----------------------------------------------*/
/**
 * @brief  the product of two matrice. If the column of first matrix does not equal to the row of second matrix, output a warning and return a zero matrix.
 * @tparam T  The elements in matrix
 * @param  a  Matrix<T>              the first matrix
 * @param  b  Matrix<T>              the second matrix
 * @return Matrix<T> 
 */
template <typename T>
Matrix<T> operator*(const Matrix<T>& a, const Matrix<T>& b)
{
    Matrix<T> ans(a.n_row,b.n_col);
    if(a.n_col != b.n_row)
        {
            ans.Error(3);
            return ans;
        }

    int iter = b.n_row;
        
    for(int i = 0 ; i < a.n_row; i++)
        for(int j = 0 ; j < b.n_col; j++)
            {
                ans.matrix_elements[ i*b.n_col + j ] = 0;
                for (int k = 0;k < iter ;k++)
                    ans.matrix_elements[ i*b.n_col + j ] += a.matrix_elements[ i * a.n_col + k ]  * b.matrix_elements[k * b.n_col + j];
            }
    
    return ans;
};

/**
 * @brief  the product of column vector and row vector.
 * @tparam T 
 * @param  a                My Param doc
 * @param  b                My Param doc
 * @return Matrix<T> 
 */
template <typename T>
Matrix<T> operator*(const ColVector<T>& a, const RowVector<T>& b)
{
    Matrix<T> ans(a.n_row,b.n_col);
    for(int i = 0; i < a.n_row ; i++)
        for(int j = 0; j < b.n_col;j++)
            {
                ans.matrix_elements[i*b.n_col + j] = a.matrix_elements[i]*b.matrix_elements[j];
            }
    return ans;
}

/**
 * @brief return the product of two matrice. If the column of first matrix does not equal to the row of second matrix, output a warning and return a zero matrix.
 * @tparam T 
 * @param  a                My Param doc
 * @param  b                My Param doc
 * @return T 
 */
template <typename T>
T operator*(const RowVector<T>& a, const ColVector<T>& b)
{
    T ans = 0;
    for(int i = 0; i < a.n_col ; i++)
        ans += a.matrix_elements[i]*b.matrix_elements[i];
    return ans;
}

/**
 * @brief 
 * @tparam T 
 * @param  a                My Param doc
 * @param  b                My Param doc
 * @return T 
 */
template <typename T>
T operator*(const ColVector<T>& a, const ColVector<T>& b)
{
    T ans = 0;
    for(int i = 0; i < a.n_row ; i++)
        ans += a.matrix_elements[i]*b.matrix_elements[i];
    return ans;
}

/**
 * @brief 
 * @tparam T 
 * @param  a                My Param doc
 * @param  b                My Param doc
 * @return T 
 */
template <typename T>
T operator*(const RowVector<T>& a, const RowVector<T>& b)
{
    T ans = 0;
    for(int i = 0; i < a.n_col ; i++)
        ans += a.matrix_elements[i]*b.matrix_elements[i];
    return ans;
}


#else
#endif
